﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Irrlicht.Net.Core;
using Irrlicht.Net.IO;

namespace Irrlicht.Net.GUI
{
    public enum GUIEventTypes
    {
        ElementFocusLost = 0,
        ElementFocused,
        ElementHovered,
        ElementLeft,
        ElementClosed,
        ButtonClicked,
        ScrollBarChanged,
        CheckboxChanged,
        ListboxChanged,
        ListboxSelectedAgain,
        FileSelected,
        DirectorySelected,
        FileChooseDialogCancelled,
        MessageboxYes,
        MessageboxNo,
        MessageboxOk,
        MessageboxCancel,
        EditboxEnter,
        EditboxChanged,
        EditboxMarkingChanged,
        TabChanged,
        MenuItemSelected,
        ComboBoxChanged,
        SpinboxChanged,
        TableChanged,
        TableHeaderChanged,
        TableSelectedAgain,
        TreeviewNodeDeselect,
        TreeviewNodeSelect,
        TreeviewNodeExpand,
        TreeviewNodeCollapse,
        Count
    };

    public class Element : AttributeExchangingObject
    {
        #region PInvoke
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern void GUIElement_AddChild(IntPtr guiElement, IntPtr child);
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern bool GUIElement_BringToFront(IntPtr guiElement, IntPtr element);
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern void GUIElement_Draw(IntPtr guiElement);
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern void GUIElement_Move(IntPtr guiElement, int x, int y);
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern void GUIElement_Remove(IntPtr guiElement);
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern void GUIElement_RemoveChild(IntPtr guiElement, IntPtr child);

        // Get Methods
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern IntPtr GUIElement_GetElementFromId(IntPtr guiElement, int id, bool searchchildren);
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern IntPtr GUIElement_GetElementFromPoint(IntPtr guiElement, int x, int y);
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern bool GUIElement_GetNextElement(IntPtr guiElement,
            int startOrder, bool reverse, bool group, IntPtr first, IntPtr closest, bool includeInvisible);
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern IntPtr GUIElement_GetTabGroup(IntPtr guiElement);

        // Set Methods
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern void GUIElement_SetTabGroup(IntPtr guiElement, bool isGroup);

        // Get Properties
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern void GUIElement_GetAbsoluteClippingRect(IntPtr guiElement, 
            ref int lowerX, ref int lowerY, ref int upperX, ref int upperY);
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern void GUIElement_GetAbsolutePosition(IntPtr guiElement, 
            ref int lowerX, ref int lowerY, ref int upperX, ref int upperY);
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern int GUIElement_GetID(IntPtr guiElement);
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern IntPtr GUIElement_GetParent(IntPtr guiElement);
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern void GUIElement_GetRelativePosition(IntPtr guiElement,
            ref int lowerX, ref int lowerY, ref int upperX, ref int upperY);
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern int GUIElement_GetTabOrder(IntPtr guiElement);
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern IntPtr GUIElement_GetText(IntPtr guiElement);
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern IntPtr GUIElement_GetToolTipText(IntPtr guiElement);
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern ElementTypes GUIElement_GetType(IntPtr guiElement);
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern IntPtr GUIElement_GetTypeName(IntPtr guiElement);
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern bool GUIElement_IsEnabled(IntPtr guiElement);
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern bool GUIElement_IsTabStop(IntPtr guiElement);
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern bool GUIElement_IsVisible(IntPtr guiElement);

        // Set Properties      
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern void GUIElement_SetEnabled(IntPtr guiElement, bool enabled);
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern void GUIElement_SetID(IntPtr guiElement, int id);
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern void GUIElement_SetTabOrder(IntPtr guiElement, int index);
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern void GUIElement_SetTabStop(IntPtr guiElement, bool enable);
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern void GUIElement_SetText(IntPtr guiElement, 
            [MarshalAs(UnmanagedType.BStr)]
            string text);
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern void GUIElement_SetToolTipText(IntPtr guiElement, 
            [MarshalAs(UnmanagedType.BStr)] 
            string text);
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern void GUIElement_SetVisible(IntPtr guiElement, bool visible);


        // Events
        [DllImport(@"IrrlichtW.dll", CallingConvention = CallingConvention.Cdecl)]
        internal static extern void GUIElement_OnPostRender(IntPtr guiElement, uint timeMs);
          
        #endregion

        public Element()
        { }

        public Element(IntPtr pointer)
        {
            this.pointer = pointer;
        }

        public Rectangle<int> AbsolutePosition
        {
            get
            {
                int lowerX = 0, lowerY = 0, upperX = 0, upperY = 0;
                GUIElement_GetAbsolutePosition(this.pointer, ref lowerX, ref lowerY, ref upperX, ref upperY);
                return new Rectangle<int> (upperX, upperY, lowerX - upperX, lowerY - upperY);
            }
        }

        public Rectangle<int> AbsoluteClippingRect
        {
            get
            {
                int lowerX = 0, lowerY = 0, upperX = 0, upperY = 0;
                GUIElement_GetAbsoluteClippingRect(this.pointer, ref lowerX, ref lowerY, ref upperX, ref upperY);
                return new Rectangle<int> (upperX, upperY, lowerX - upperX, lowerY - upperY);
            }
        }

        public virtual bool Enabled
        {
            get
            {
                return GUIElement_IsEnabled(this.pointer);
            }

            set
            {
                GUIElement_SetEnabled(this.pointer, value);
            }
        }

        public virtual int ID 
        { 
            get
            {
                return GUIElement_GetID(this.pointer);
            }
            set
            {
                GUIElement_SetID(this.pointer, value);
            }
        }

        public Element Parent 
        { 
            get         
            {
                return new Element(GUIElement_GetParent(this.pointer));
            }
        }

        public Rectangle<int> RelativePosition 
        {
            get
            {
                int lowerX = 0, lowerY = 0, upperX = 0, upperY = 0;
                GUIElement_GetRelativePosition(this.pointer, ref lowerX, ref lowerY, ref upperX, ref upperY);
                return new Rectangle<int> (upperX, upperY, lowerX - upperX, lowerY - upperY);
           }

            set
            {
            }
        }

        public int TabOrder
        {
            get
            {
                return GUIElement_GetTabOrder(this.pointer);
            }

            set
            {
                GUIElement_SetTabOrder(this.pointer, value);
            }
        }

        public bool TabStop
        {
            get
            {
                return GUIElement_IsTabStop(this.pointer);
            }

            set
            {        
                GUIElement_SetTabStop(this.pointer, value);
            }
        }

        public virtual string Text 
        { 
            get
            {
                IntPtr text = GUIElement_GetText(this.pointer);
                return Marshal.PtrToStringUni(text);
            }
            
            set
            {
                GUIElement_SetText(this.pointer, value);
            }
        }

        public string ToolTipText
        {
            get
            {
                IntPtr text = GUIElement_GetToolTipText(this.pointer);
                return Marshal.PtrToStringUni(text);
            }

            set
            {
                GUIElement_SetToolTipText(this.pointer, value);
            }
        }

        public ElementTypes Type
        {
            get
            {
                return GUIElement_GetType(this.pointer);
            }
        }

        public virtual string TypeName
        {
            get
            {
                IntPtr text = GUIElement_GetTypeName(this.pointer);
                return Marshal.PtrToStringAnsi(text);
            }
        }

        public virtual bool Visible
        {
            get
            {
                return GUIElement_IsVisible(this.pointer);
            }

            set
            {
                GUIElement_SetVisible(this.pointer, value);
            }
        }

        /// <summary>
        /// Adds a GUI element as new child of this element. 
        /// </summary>
        /// <param name="child">Child Element</param>
        public virtual void AddChild(Element child)
        {
            GUIElement_AddChild(this.pointer, child.pointer);
        }

        /// <summary>
        /// Brings a child to front.
        /// </summary>
        /// <param name="element">Child element to bring to front.</param>
        /// <returns></returns>
        public virtual bool BringToFront(Element element)
        {
            return GUIElement_BringToFront(this.pointer, element.pointer);
        }

        /// <summary>
        /// Draws the element and its children.
        /// </summary>
        public virtual void Draw()
        {
            GUIElement_Draw(this.pointer);
        }

        /// <summary>
        /// Returns the topmost GUI element at the specific position.
        /// </summary>
        /// <param name="x">X position of the point</param>
        /// <param name="y">Y position of the point</param>
        /// <returns>GUI Element at the specified point</returns>
        public Element GetElementFromPoint(int x, int y)
        {
            return new Element(GUIElement_GetElementFromPoint(this.pointer, x, y));
        }

        /// <summary>
        /// Returns the topmost GUI element at the specific position. 
        /// </summary>
        /// <param name="point">Point to find the GUI element</param>
        /// <returns>GUI Element at the specified point</returns>
        public Element GetElementFromPoint(Position2D<int> point)
        {
            return GetElementFromPoint(point.X, point.Y);
        }
        
        /// <summary>
        /// Finds the first element with the given id excluding child elements.
        /// </summary>
        /// <param name="id">ID of the GUI Element</param>
        /// <returns>GUI Element that matches the ID</returns>
        public virtual Element GetElementFromID(int id)
        {
            return GetElementFromID(id, false);
        }

        /// <summary>
        /// Finds the first element with the given id.
        /// </summary>
        /// <param name="id">ID of the GUI Element</param>
        /// <param name="searchchildren">Specifies whether child elements are searched.</param>
        /// <returns>GUI Element that matches the ID</returns>
        public Element GetElementFromID(int id, bool searchchildren)
        {
            return new Element(GUIElement_GetElementFromId(this.pointer, id, searchchildren));
        }

        /// <summary>
        /// Searches elements to find the closest next element to tab to excluding invisible elements.
        /// </summary>
        /// <param name="startOrder">Start tab order</param>
        /// <param name="reverse">Specifies whether a reverse search is performed.</param>
        /// <param name="group">Indicates tab group.</param>
        /// <param name="first">First Element</param>
        /// <param name="closest">Closest GUI element</param>
        /// <returns></returns>
        public bool GetNextElement(int startOrder, bool reverse, bool group, Element first, Element closest)
        {
            return GetNextElement(startOrder, reverse, group, first, closest, false);
        }

        /// <summary>
        /// Searches elements to find the closest next element to tab to.
        /// </summary>
        /// <param name="startOrder">Start tab order</param>
        /// <param name="reverse">Specifies whether a reverse search is performed.</param>
        /// <param name="group">Indicates tab group.</param>
        /// <param name="first">First Element</param>
        /// <param name="closest">Closest GUI element</param>
        /// <param name="includeInvisible">Indicates whether invisible elements are included.</param>
        /// <returns></returns>
        public bool GetNextElement(int startOrder, bool reverse, bool group, Element first, Element closest, bool includeInvisible)
        {
            return GUIElement_GetNextElement(this.pointer, startOrder, reverse, group, first.pointer, closest.pointer, includeInvisible);
        }

        /// <summary>
        /// Gets the container element which holds all elements in this element's tab group. 
        /// </summary>
        /// <returns>Returns the container element which holds all elements in this element's tab group.</returns>
        public Element GetTabGroup()
        {
            return new Element(GUIElement_GetTabGroup(this.pointer));
        }

        /// <summary>
        /// Moves this element. 
        /// </summary>
        /// <param name="x">X position to move to.</param>
        /// <param name="y">Y position to move to.</param>
        public virtual void Move(int x, int y)
        {
            GUIElement_Move(this.pointer, x, y);
        }

        /// <summary>
        /// Moves this element. 
        /// </summary>
        /// <param name="absoluteMovement">Coordinates to move to.</param>
        public void Move(Position2D<int> absoluteMovement)
        {
            Move(absoluteMovement.X, absoluteMovement.Y);
        }

        /// <summary>
        /// Removes this element from its parent.
        /// </summary>
        public virtual void Remove()
        {
            GUIElement_Remove(this.pointer);
        }

        /// <summary>
        /// Removes a child. 
        /// </summary>
        /// <param name="child">Child to be removed</param>
        public virtual void RemoveChild(Element child)
        {
            GUIElement_RemoveChild(pointer, child.pointer);
        }

        /// <summary>
        /// Sets whether this element is a container for a group of elements which can be navigated using the tab key.
        /// </summary>
        /// <param name="isGroup">Indicated whether this element is a tab group container</param>
        public void SetTabGroup(bool isGroup)
        {
            GUIElement_SetTabGroup(this.pointer, isGroup);
        }        

        public virtual void UpdateAbsolutePosition()
        {
        }

        public virtual void OnPostRender(uint timeMs)
        {
            GUIElement_OnPostRender(this.pointer, timeMs);
        }
    }
}
